/*
 * Copyright (C) 2011 Google Inc. All rights reserved.
 * Copyright (C) 2018, Alibaba Group.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @implements {Profiler.ProfileType.DataDisplayDelegate}
 * @implements {UI.Searchable}
 * @unrestricted
 */
Profiler.HeapSnapshotView = class extends UI.SimpleView {
    /**
     * @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate
     * @param {!Profiler.HeapProfileHeader} profile
     */
    constructor(dataDisplayDelegate, profile) {

        super(Common.UIString('Heap Snapshot'));

        this.element.classList.add('heap-snapshot-view');
        this._profile = profile;

        profile.profileType().addEventListener(
            Profiler.HeapSnapshotProfileType.SnapshotReceived, this._onReceiveSnapshot, this);
        profile.profileType().addEventListener(
            Profiler.ProfileType.Events.RemoveProfileHeader, this._onProfileHeaderRemoved, this);

        var isHeapTimeline = profile.profileType().id === Profiler.TrackingHeapSnapshotProfileType.TypeId;
        if (isHeapTimeline) {
            this._trackingOverviewGrid = new Profiler.HeapTrackingOverviewGrid(profile);
            this._trackingOverviewGrid.addEventListener(
                Profiler.HeapTrackingOverviewGrid.IdsRangeChanged, this._onIdsRangeChanged.bind(this));
        }

        this._parentDataDisplayDelegate = dataDisplayDelegate;

        this._searchableView = new UI.SearchableView(this);
        this._searchableView.show(this.element);

        this._splitWidget = new UI.SplitWidget(false, true, 'heapSnapshotSplitViewState', 200, 200);
        this._splitWidget.show(this._searchableView.element);

        this._containmentDataGrid = new Profiler.HeapSnapshotContainmentDataGrid(this);
        this._containmentDataGrid.addEventListener(DataGrid.DataGrid.Events.SelectedNode, this._selectionChanged, this);
        this._containmentWidget = this._containmentDataGrid.asWidget();
        this._containmentWidget.setMinimumSize(50, 25);

        this._reportView = new Profiler.HeapSnapshotReportView();
        this._statisticsView = new Profiler.HeapSnapshotStatisticsView();

        /* 对象列表视图 */
        this._objectListDataGrid = new Profiler.HeapSnapshotObjectListDataGrid(this);
        this._objectListDataGrid.addEventListener(DataGrid.DataGrid.Events.SelectedNode, this._selectionChanged, this);
        this._objectListWidget = this._objectListDataGrid.asWidget();
        this._objectListWidget.setMinimumSize(50, 25);
        
        this._constructorsDataGrid = new Profiler.HeapSnapshotConstructorsDataGrid(this);
        this._constructorsDataGrid.addEventListener(DataGrid.DataGrid.Events.SelectedNode, this._selectionChanged, this);
        this._constructorsWidget = this._constructorsDataGrid.asWidget();
        this._constructorsWidget.setMinimumSize(50, 25);

        this._diffDataGrid = new Profiler.HeapSnapshotDiffDataGrid(this);
        this._diffDataGrid.addEventListener(DataGrid.DataGrid.Events.SelectedNode, this._selectionChanged, this);
        this._diffWidget = this._diffDataGrid.asWidget();
        this._diffWidget.setMinimumSize(50, 25);

        if (isHeapTimeline && Common.moduleSetting('recordAllocationStacks').get()) {
            this._allocationDataGrid = new Profiler.AllocationDataGrid(profile._heapProfilerModel, this);
            this._allocationDataGrid.addEventListener(
                DataGrid.DataGrid.Events.SelectedNode, this._onSelectAllocationNode, this);
            this._allocationWidget = this._allocationDataGrid.asWidget();
            this._allocationWidget.setMinimumSize(50, 25);

            this._allocationStackView = new Profiler.HeapAllocationStackView(profile._heapProfilerModel);
            this._allocationStackView.setMinimumSize(50, 25);

            this._tabbedPane = new UI.TabbedPane();
        }

        this._retainmentDataGrid = new Profiler.HeapSnapshotRetainmentDataGrid(this);
        this._retainmentWidget = this._retainmentDataGrid.asWidget();
        this._retainmentWidget.setMinimumSize(50, 21);
        this._retainmentWidget.element.classList.add('retaining-paths-view');

        var splitWidgetResizer;
        if (this._allocationStackView) {
            this._tabbedPane = new UI.TabbedPane();

            this._tabbedPane.appendTab('retainers', Common.UIString('Retainers'), this._retainmentWidget);
            this._tabbedPane.appendTab('allocation-stack', Common.UIString('Allocation stack'), this._allocationStackView);

            splitWidgetResizer = this._tabbedPane.headerElement();
            this._objectDetailsView = this._tabbedPane;
        } else {
            var retainmentViewHeader = createElementWithClass('div', 'heap-snapshot-view-resizer');
            var retainingPathsTitleDiv = retainmentViewHeader.createChild('div', 'title');
            var retainingPathsTitle = retainingPathsTitleDiv.createChild('span');
            retainingPathsTitle.textContent = Common.UIString('引用链视图(Retainers)');

            splitWidgetResizer = retainmentViewHeader;
            this._objectDetailsView = new UI.VBox();
            this._objectDetailsView.element.appendChild(retainmentViewHeader);
            this._retainmentWidget.show(this._objectDetailsView.element);
        }
        this._splitWidget.hideDefaultResizer();
        this._splitWidget.installResizer(splitWidgetResizer);

        this._retainmentDataGrid.addEventListener(
            DataGrid.DataGrid.Events.SelectedNode, this._inspectedObjectChanged, this);
        this._retainmentDataGrid.reset();

        this._perspectives = [];
        this._comparisonPerspective = new Profiler.HeapSnapshotView.ComparisonPerspective();
        this._perspectives.push(new Profiler.HeapSnapshotView.ReportPerspective());
        this._perspectives.push(new Profiler.HeapSnapshotView.SummaryPerspective());
        this._perspectives.push(new Profiler.HeapSnapshotView.ObjectListPerspective());

        if (profile.profileType() !== Profiler.ProfileTypeRegistry.instance.trackingHeapSnapshotProfileType)
            this._perspectives.push(this._comparisonPerspective);
        this._perspectives.push(new Profiler.HeapSnapshotView.ContainmentPerspective());
        if (this._allocationWidget)
            this._perspectives.push(new Profiler.HeapSnapshotView.AllocationPerspective());
        this._perspectives.push(new Profiler.HeapSnapshotView.StatisticsPerspective());


        this._perspectiveSelect = new UI.ToolbarComboBox(this._onSelectedPerspectiveChanged.bind(this));
        this._updatePerspectiveOptions();

        this._baseSelect = new UI.ToolbarComboBox(this._changeBase.bind(this));
        this._baseSelect.setVisible(false);
        this._updateBaseOptions();

        this._filterSelect = new UI.ToolbarComboBox(this._changeFilter.bind(this));
        this._filterSelect.setVisible(false);
        this._updateFilterOptions();

        this._classNameFilter = new UI.ToolbarInput('Class filter');
        this._classNameFilter.setVisible(false);
        this._constructorsDataGrid.setNameFilter(this._classNameFilter);
        this._diffDataGrid.setNameFilter(this._classNameFilter);
        this._objectListDataGrid.setNameFilter(this._classNameFilter);

        /******* add ********/
        this._searchObjectByID = new UI.ToolbarInput("Search By Object ID");
        this._searchObjectByID.setVisible(false);
        this._searchObjectByID.addEventListener(UI.ToolbarInput.Event.TextChanged, this._changedObjectID.bind(this));

        this._selectedSizeText = new UI.ToolbarText();

        this._popoverHelper = new UI.PopoverHelper(this.element, this._getPopoverRequest.bind(this));
        this._popoverHelper.setDisableOnClick(true);
        this._popoverHelper.setHasPadding(true);
        this.element.addEventListener('scroll', this._popoverHelper.hidePopover.bind(this._popoverHelper), true);

        this._currentPerspectiveIndex = 0;
        this._currentPerspective = this._perspectives[0];
        this._currentPerspective.activate(this);
        this._dataGrid = this._currentPerspective.masterGrid(this);
        this._populate();
        this._searchThrottler = new Common.Throttler(0);

        for (var existingProfile of this._profiles())
            existingProfile.addEventListener(Profiler.ProfileHeader.Events.ProfileTitleChanged, this._updateControls, this);


    }

    _changedObjectID(event){
        var data = event.data;
        var interval = Profiler.HeapSnapshotView.ConstValues.INTERVAL;

        if(data == undefined || data == null || isNaN(data)) return;
        clearTimeout(this.SetTimeoutID);
        this.SetTimeoutID = setTimeout(()=>{
            this._dataGrid.setSearchID(data);
        }, interval)

    }
    /**
     * @return {!UI.SearchableView}
     */
    searchableView() {
        return this._searchableView;
    }

    /**
     * @override
     * @param {?Profiler.ProfileHeader} profile
     * @return {?UI.Widget}
     */
    showProfile(profile) {
        return this._parentDataDisplayDelegate.showProfile(profile);
    }

    /**
     * @override
     * @param {!Protocol.HeapProfiler.HeapSnapshotObjectId} snapshotObjectId
     * @param {string} perspectiveName
     */
    showObject(snapshotObjectId, perspectiveName) {
        if (snapshotObjectId <= this._profile.maxJSObjectId)
            this.selectLiveObject(perspectiveName, snapshotObjectId);
        else
            this._parentDataDisplayDelegate.showObject(snapshotObjectId, perspectiveName);
    }

    async _populate() {
        var heapSnapshotProxy = await this._profile._loadPromise;

        this._retrieveStatistics(heapSnapshotProxy);
        this._getReportData()

        if(this._dataGrid)
            this._dataGrid.setDataSource(heapSnapshotProxy);

        if (this._profile.profileType().id === Profiler.TrackingHeapSnapshotProfileType.TypeId &&
            this._profile.fromFile()) {
            var samples = await heapSnapshotProxy.getSamples();
            this._trackingOverviewGrid._setSamples(samples);
        }

        var list = this._profiles();
        var profileIndex = list.indexOf(this._profile);
        this._baseSelect.setSelectedIndex(Math.max(0, profileIndex - 1));
        if (this._trackingOverviewGrid)
            this._trackingOverviewGrid._updateGrid();
    }

    /**
     * @param {!Profiler.HeapSnapshotProxy} heapSnapshotProxy
     * @return {!Promise<!HeapSnapshotModel.Statistics>}
     */
    async _retrieveStatistics(heapSnapshotProxy) {
        var statistics = await heapSnapshotProxy.getStatistics();
        this._statisticsView.setTotal(statistics.total);
        this._statisticsView.addRecord(statistics.code, Common.UIString('Code'), '#f77');
        this._statisticsView.addRecord(statistics.strings, Common.UIString('Strings'), '#5e5');
        this._statisticsView.addRecord(statistics.jsArrays, Common.UIString('JS Arrays'), '#7af');
        this._statisticsView.addRecord(statistics.native, Common.UIString('Typed Arrays'), '#fc5');
        this._statisticsView.addRecord(statistics.system, Common.UIString('System Objects'), '#98f');
        this._statisticsView.addRecord(statistics.total, Common.UIString('Total'));
        return statistics;
    }

    _getReportData() {
        var _this = this;
        var list = this._profiles();
        var trendResults = []; // 趋势数据
        var promiseArrs = [];
        for (var i = 0; i < list.length; i++) {
            var heapSnapshotProxy = list[i]._snapshotProxy;
            trendResults.push({
                uid: list[i].uid,
                title: list[i].title,
                nodeCount: heapSnapshotProxy.nodeCount,
                totalSize: heapSnapshotProxy.totalSize
            })
        }

        var trendInfo = this._getChangeTrend(trendResults)
        var breakIndex = trendInfo.breakIndex;

        promiseArrs.push(_this._getLeakObjectsInfo())
        promiseArrs.push(_this._getDiffInfo(breakIndex, trendInfo))
        Promise.all(promiseArrs).then(function(){
            _this._reportView.init();
        })

    }

    _getChangeTrend(items){
        var len = items.length;
        var up_tails_count = 0;
        var result = {
            isTailsUp: false,
            isBigLeak: false,
            isAlwaysUp: true,
            breakIndex: 1,
            isFilesCountEnough: true
        };

        if(len < 4) {
            result.isFilesCountEnough = false
        }else{
            for(var i = len-1; i>0; i--){
                if(items[i].totalSize-items[i-1].totalSize > 0){
                    up_tails_count++;
                }else{
                    result.isAlwaysUp = false;
                    result.breakIndex = i;
                    break;
                }
            }
            if(up_tails_count/len>=0.5) result.isTailsUp = true;
            if(result.isTailsUp && (items[len-1].totalSize-items[result.breakIndex].totalSize)/1024>500) result.isBigLeak = true;
        }

        this._reportView.setTrendData(result, items);
        return result;
    }

    _getLeakObjectsInfo(){
      var list = this._profiles();
      var _this = this;
     
      if(_this._parentDataDisplayDelegate._recommendData){
        return Promise.resolve(_this._parentDataDisplayDelegate._recommendData).then(function(data){
          _this._reportView.setLeakItems(data);
        });
      }else{
          return list[list.length-1]._snapshotProxy.calculateLeakObjects().then(function(data){
            _this._parentDataDisplayDelegate._recommendData = data;
            _this._reportView.setLeakItems(data);
            return data;
          })
      }
    }

    _getDiffInfo(breakIndex, trendInfo){
      var _this = this;
      var list = _this._profiles();
      var promiseArr = [];
      var index = list.length-1;

      var lastProxy = list[index]._snapshotProxy;
      var firstProxy = "";

    
      if(trendInfo.isFilesCountEnough && (trendInfo.isAlwaysUp || trendInfo.isTailsUp)){
        for(var i = breakIndex; i<list.length-1;i++){
          firstProxy = list[i]._snapshotProxy;
          if(_this._parentDataDisplayDelegate._diffsArray){
            _this._parentDataDisplayDelegate._diffsArray.map(function(data){
                return promiseArr.push(Promise.resolve(data));
            })
             break;
          }else{
            promiseArr.push(lastProxy.makeDiffBetAggregates(lastProxy._aggregate, firstProxy._aggregatesForDiff))
          }
        }
      }
      return Promise.all(promiseArr).then(function(diffsArray){
          if(!_this._parentDataDisplayDelegate._diffsArray){
            _this._parentDataDisplayDelegate._diffsArray = diffsArray
          }
          if(diffsArray.length!=0){
            _this._reportView.setDiffsArrayData(diffsArray);
            _this._reportView.setDiffData(diffsArray[0]);
          }
      })
    }

    /**
     * @param {!Common.Event} event
     */
    _onIdsRangeChanged(event) {
        var minId = event.data.minId;
        var maxId = event.data.maxId;
        this._selectedSizeText.setText(Common.UIString('Selected size: %s', Number.bytesToString(event.data.size)));
        if (this._constructorsDataGrid.snapshot)
            this._constructorsDataGrid.setSelectionRange(minId, maxId);
    }

    /**
     * @override
     * @return {!Array<!UI.ToolbarItem>}
     */
    syncToolbarItems() {
        var result = [this._perspectiveSelect, this._classNameFilter, this._searchObjectByID];
        if (this._profile.profileType() !== Profiler.ProfileTypeRegistry.instance.trackingHeapSnapshotProfileType)
            result.push(this._baseSelect, this._filterSelect);
        result.push(this._selectedSizeText);
        return result;
    }

    /**
     * @override
     */
    willHide() {
        this._currentSearchResultIndex = -1;
        this._popoverHelper.hidePopover();
    }

    /**
     * @override
     * @return {boolean}
     */
    supportsCaseSensitiveSearch() {
        return true;
    }

    /**
     * @override
     * @return {boolean}
     */
    supportsRegexSearch() {
        return false;
    }

    /**
     * @override
     */
    searchCanceled() {
        this._currentSearchResultIndex = -1;
        this._searchResults = [];
    }

    /**
     * @param {?Profiler.HeapSnapshotGridNode} node
     */
    _selectRevealedNode(node) {
        if (node)
            node.select();
    }

    /**
     * @override
     * @param {!UI.SearchableView.SearchConfig} searchConfig
     * @param {boolean} shouldJump
     * @param {boolean=} jumpBackwards
     */
    performSearch(searchConfig, shouldJump, jumpBackwards) {
        var nextQuery = new HeapSnapshotModel.SearchConfig(
            searchConfig.query.trim(), searchConfig.caseSensitive, searchConfig.isRegex, shouldJump,
            jumpBackwards || false);

        this._searchThrottler.schedule(this._performSearch.bind(this, nextQuery));
    }

    /**
     * @param {!HeapSnapshotModel.SearchConfig} nextQuery
     * @return {!Promise}
     */
    async _performSearch(nextQuery) {
        // Call searchCanceled since it will reset everything we need before doing a new search.
        this.searchCanceled();

        if (!this._currentPerspective.supportsSearch())
            return;

        this.currentQuery = nextQuery;
        var query = nextQuery.query.trim();

        if (!query)
            return;

        if (query.charAt(0) === '@') {
            var snapshotNodeId = parseInt(query.substring(1), 10);
            if (isNaN(snapshotNodeId))
                return;
            var node = await this._dataGrid.revealObjectByHeapSnapshotId(String(snapshotNodeId));
            this._selectRevealedNode(node);
            return;
        }

        this._searchResults = await this._profile._snapshotProxy.search(this.currentQuery, this._dataGrid.nodeFilter());

        this._searchableView.updateSearchMatchesCount(this._searchResults.length);
        if (this._searchResults.length)
            this._currentSearchResultIndex = nextQuery.jumpBackwards ? this._searchResults.length - 1 : 0;
        return this._jumpToSearchResult(this._currentSearchResultIndex);
    }

    /**
     * @override
     */
    jumpToNextSearchResult() {
        if (!this._searchResults.length)
            return;
        this._currentSearchResultIndex = (this._currentSearchResultIndex + 1) % this._searchResults.length;
        this._searchThrottler.schedule(this._jumpToSearchResult.bind(this, this._currentSearchResultIndex));
    }

    /**
     * @override
     */
    jumpToPreviousSearchResult() {
        if (!this._searchResults.length)
            return;
        this._currentSearchResultIndex =
            (this._currentSearchResultIndex + this._searchResults.length - 1) % this._searchResults.length;
        this._searchThrottler.schedule(this._jumpToSearchResult.bind(this, this._currentSearchResultIndex));
    }

    /**
     * @param {number} searchResultIndex
     * @return {!Promise}
     */
    async _jumpToSearchResult(searchResultIndex) {
        this._searchableView.updateCurrentMatchIndex(searchResultIndex);
        var node = await this._dataGrid.revealObjectByHeapSnapshotId(String(this._searchResults[searchResultIndex]));
        this._selectRevealedNode(node);
    }

    refreshVisibleData() {
        if (!this._dataGrid)
            return;
        var child = this._dataGrid.rootNode().children[0];
        while (child) {
            child.refresh();
            child = child.traverseNextNode(false, null, true);
        }
    }

    _changeBase() {
        if (this._baseProfile === this._profiles()[this._baseSelect.selectedIndex()])
            return;

        this._baseProfile = this._profiles()[this._baseSelect.selectedIndex()];
        var dataGrid = /** @type {!Profiler.HeapSnapshotDiffDataGrid} */ (this._dataGrid);
        // Change set base data source only if main data source is already set.
        if (dataGrid.snapshot)
            this._baseProfile._loadPromise.then(dataGrid.setBaseDataSource.bind(dataGrid));

        if (!this.currentQuery || !this._searchResults)
            return;

        // The current search needs to be performed again. First negate out previous match
        // count by calling the search finished callback with a negative number of matches.
        // Then perform the search again with the same query and callback.
        this.performSearch(this.currentQuery, false);
    }

    _changeFilter() {
        var profileIndex = this._filterSelect.selectedIndex() - 1;
        this._dataGrid.filterSelectIndexChanged(this._profiles(), profileIndex);

        if (!this.currentQuery || !this._searchResults)
            return;

        // The current search needs to be performed again. First negate out previous match
        // count by calling the search finished callback with a negative number of matches.
        // Then perform the search again with the same query and callback.
        this.performSearch(this.currentQuery, false);
    }

    /**
     * @return {!Array.<!Profiler.ProfileHeader>}
     */
    _profiles() {
        return this._profile.profileType().getProfiles();
    }

    /**
     * @param {!UI.ContextMenu} contextMenu
     * @param {!Event} event
     */
    populateContextMenu(contextMenu, event) {
        if (this._dataGrid)
            this._dataGrid.populateContextMenu(contextMenu, event);
    }

    /**
     * @param {!Common.Event} event
     */
    _selectionChanged(event) {
        var selectedNode = /** @type {!Profiler.HeapSnapshotGridNode} */ (event.data);
        this._setSelectedNodeForDetailsView(selectedNode);
        this._inspectedObjectChanged(event);
    }

    /**
     * @param {!Common.Event} event
     */
    _onSelectAllocationNode(event) {
        var selectedNode = /** @type {!DataGrid.DataGridNode} */ (event.data);
        this._constructorsDataGrid.setAllocationNodeId(selectedNode.allocationNodeId());
        this._setSelectedNodeForDetailsView(null);
    }

    /**
     * @param {!Common.Event} event
     */
    _inspectedObjectChanged(event) {
        var selectedNode = /** @type {!DataGrid.DataGridNode} */ (event.data);
        if (this._profile._heapProfilerModel && selectedNode instanceof Profiler.HeapSnapshotGenericObjectNode)
            this._profile._heapProfilerModel.addInspectedHeapObject(String(selectedNode.snapshotNodeId));
    }

    /**
     * @param {?Profiler.HeapSnapshotGridNode} nodeItem
     */
    _setSelectedNodeForDetailsView(nodeItem) {
        var dataSource = nodeItem && nodeItem.retainersDataSource();
        if (dataSource) {
            this._retainmentDataGrid.setDataSource(dataSource.snapshot, dataSource.snapshotNodeIndex);
            if (this._allocationStackView)
                this._allocationStackView.setAllocatedObject(dataSource.snapshot, dataSource.snapshotNodeIndex);
        } else {
            if (this._allocationStackView)
                this._allocationStackView.clear();
            this._retainmentDataGrid.reset();
        }
    }

    /**
     * @param {string} perspectiveTitle
     * @return {!Promise}
     */
    _changePerspectiveAndWait(perspectiveTitle) {
        const perspectiveIndex = this._perspectives.findIndex(perspective => perspective.title() === perspectiveTitle);
        if (perspectiveIndex === -1 || this._currentPerspectiveIndex === perspectiveIndex)
            return Promise.resolve();

        var promise = this._perspectives[perspectiveIndex].masterGrid(this).once(
            Profiler.HeapSnapshotSortableDataGrid.Events.ContentShown);

        const option = this._perspectiveSelect.options().find(option => option.value === perspectiveIndex);
        this._perspectiveSelect.select(/** @type {!Element} */(option));
        this._changePerspective(perspectiveIndex);
        return promise;
    }

    async _updateDataSourceAndView() {
        var dataGrid = this._dataGrid;
        if (!dataGrid || dataGrid.snapshot)
            return;

        var snapshotProxy = await this._profile._loadPromise;

        if (this._dataGrid !== dataGrid)
            return;
        if (dataGrid.snapshot !== snapshotProxy)
            dataGrid.setDataSource(snapshotProxy);
        if (dataGrid !== this._diffDataGrid)
            return;
        if (!this._baseProfile)
            this._baseProfile = this._profiles()[this._baseSelect.selectedIndex()];

        var baseSnapshotProxy = await this._baseProfile._loadPromise;

        if (this._diffDataGrid.baseSnapshot !== baseSnapshotProxy)
            this._diffDataGrid.setBaseDataSource(baseSnapshotProxy);
    }

    /**
     * @param {!Event} event
     */
    _onSelectedPerspectiveChanged(event) {
        this._changePerspective(event.target.selectedOptions[0].value);
    }

    /**
     * @param {number} selectedIndex
     */
    _changePerspective(selectedIndex) {
        if (selectedIndex === this._currentPerspectiveIndex)
            return;

        this._currentPerspectiveIndex = selectedIndex;

        this._currentPerspective.deactivate(this);
        var perspective = this._perspectives[selectedIndex];
        this._currentPerspective = perspective;
        this._dataGrid = perspective.masterGrid(this);
        perspective.activate(this);

        this.refreshVisibleData();
        if (this._dataGrid)
            this._dataGrid.updateWidths();

        this._updateDataSourceAndView();

        if (!this.currentQuery || !this._searchResults)
            return;

        // The current search needs to be performed again. First negate out previous match
        // count by calling the search finished callback with a negative number of matches.
        // Then perform the search again the with same query and callback.
        this.performSearch(this.currentQuery, false);
    }

    /**
     * @param {string} perspectiveName
     * @param {!Protocol.HeapProfiler.HeapSnapshotObjectId} snapshotObjectId
     */
    async selectLiveObject(perspectiveName, snapshotObjectId) {
        await this._changePerspectiveAndWait(perspectiveName);
        var node = await this._dataGrid.revealObjectByHeapSnapshotId(snapshotObjectId);
        if (node)
            node.select();
        else
            Common.console.error('Cannot find corresponding heap snapshot node');
    }

    /**
     * @param {!Event} event
     * @return {?UI.PopoverRequest}
     */
    _getPopoverRequest(event) {
        var span = event.target.enclosingNodeOrSelfWithNodeName('span');
        var row = event.target.enclosingNodeOrSelfWithNodeName('tr');
        var heapProfilerModel = this._profile._heapProfilerModel;
        if (!row || !span || !heapProfilerModel)
            return null;
        var node = row._dataGridNode;
        var objectPopoverHelper;
        return {
            box: span.boxInWindow(),
            show: async popover => {
                var remoteObject = await node.queryObjectContent(heapProfilerModel, 'popover');
                if (!remoteObject)
                    return false;
                objectPopoverHelper = await ObjectUI.ObjectPopoverHelper.buildObjectPopover(remoteObject, popover);
                if (!objectPopoverHelper) {
                    heapProfilerModel.runtimeModel().releaseObjectGroup('popover');
                    return false;
                }
                return true;
            },
            hide: () => {
                heapProfilerModel.runtimeModel().releaseObjectGroup('popover');
                objectPopoverHelper.dispose();
            }
        };
    }

    _updatePerspectiveOptions() {
        const multipleSnapshots = this._profiles().length > 1;
        this._perspectiveSelect.removeOptions();
        this._perspectives.forEach((perspective, index) => {
            if (multipleSnapshots || perspective !== this._comparisonPerspective)
                this._perspectiveSelect.createOption(perspective.title(), '', String(index));
        });
    }

    _updateBaseOptions() {
        var list = this._profiles();
        var selectedIndex = this._baseSelect.selectedIndex();

        this._baseSelect.removeOptions();
        for (var item of list)
            this._baseSelect.createOption(item.title);

        if (selectedIndex > -1)
            this._baseSelect.setSelectedIndex(selectedIndex);
    }

    _updateFilterOptions() {
        var list = this._profiles();
        var selectedIndex = this._filterSelect.selectedIndex();

        this._filterSelect.removeOptions();
        this._filterSelect.createOption(Common.UIString('All objects'));
        for (var i = 0; i < list.length; ++i) {
            var title;
            if (!i)
                title = Common.UIString('Objects allocated before %s', list[i].title);
            else
                title = Common.UIString('Objects allocated between %s and %s', list[i - 1].title, list[i].title);
            this._filterSelect.createOption(title);
        }

        if (selectedIndex > -1)
            this._filterSelect.setSelectedIndex(selectedIndex);
    }

    _updateControls() {
        this._updatePerspectiveOptions();
        this._updateBaseOptions();
        this._updateFilterOptions();
    }

    /**
     * @param {!Common.Event} event
     */
    _onReceiveSnapshot(event) {
        this._updateControls();
        var profile = event.data;
        profile.addEventListener(Profiler.ProfileHeader.Events.ProfileTitleChanged, this._updateControls, this);
    }

    /**
     * @param {!Common.Event} event
     */
    _onProfileHeaderRemoved(event) {
        var profile = event.data;
        profile.removeEventListener(Profiler.ProfileHeader.Events.ProfileTitleChanged, this._updateControls, this);

        if (this._profile === profile) {
            this.detach();
            this._profile.profileType().removeEventListener(
                Profiler.HeapSnapshotProfileType.SnapshotReceived, this._onReceiveSnapshot, this);
            this._profile.profileType().removeEventListener(
                Profiler.ProfileType.Events.RemoveProfileHeader, this._onProfileHeaderRemoved, this);
            this.dispose();
        } else {
            this._updateControls();
        }
    }

    dispose() {
        if (this._allocationStackView) {
            this._allocationStackView.clear();
            this._allocationDataGrid.dispose();
        }
        if (this._trackingOverviewGrid)
            this._trackingOverviewGrid.dispose();
    }
};
Profiler.HeapSnapshotView.ConstValues = {
    INTERVAL:  500
}
/**
 * @unrestricted
 */
Profiler.HeapSnapshotView.Perspective = class {
    /**
     * @param {string} title
     */
    constructor(title) {
        this._title = title;
    }

    /**
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     */
    activate(heapSnapshotView) {
    }

    /**
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     */
    deactivate(heapSnapshotView) {
        heapSnapshotView._baseSelect.setVisible(false);
        heapSnapshotView._filterSelect.setVisible(false);
        heapSnapshotView._classNameFilter.setVisible(false);
        heapSnapshotView._searchObjectByID.setVisible(false);
        if (heapSnapshotView._trackingOverviewGrid)
            heapSnapshotView._trackingOverviewGrid.detach();
        if (heapSnapshotView._allocationWidget)
            heapSnapshotView._allocationWidget.detach();
        if (heapSnapshotView._statisticsView)
            heapSnapshotView._statisticsView.detach();
        if(heapSnapshotView._reportView){
            heapSnapshotView._reportView.detach();
        }

        heapSnapshotView._splitWidget.detach();
        heapSnapshotView._splitWidget.detachChildWidgets();
    }

    /**
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     * @return {?DataGrid.DataGrid}
     */
    masterGrid(heapSnapshotView) {
        return null;
    }

    /**
     * @return {string}
     */
    title() {
        return this._title;
    }

    /**
     * @return {boolean}
     */
    supportsSearch() {
        return false;
    }
};

/**
 * @unrestricted
 */
Profiler.HeapSnapshotView.SummaryPerspective = class extends Profiler.HeapSnapshotView.Perspective {
    constructor() {
        super(Common.UIString('Summary'));
    }

    /**
     * @override
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     */
    activate(heapSnapshotView) {

        heapSnapshotView._splitWidget.setMainWidget(heapSnapshotView._constructorsWidget);
        heapSnapshotView._splitWidget.setSidebarWidget(heapSnapshotView._objectDetailsView);
        heapSnapshotView._splitWidget.show(heapSnapshotView._searchableView.element);
        heapSnapshotView._filterSelect.setVisible(true);
        heapSnapshotView._classNameFilter.setVisible(true);
        if (!heapSnapshotView._trackingOverviewGrid)
            return;
        heapSnapshotView._trackingOverviewGrid.show(
            heapSnapshotView._searchableView.element, heapSnapshotView._splitWidget.element);
        heapSnapshotView._trackingOverviewGrid.update();
        heapSnapshotView._trackingOverviewGrid._updateGrid();
    }

    /**
     * @override
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     * @return {?DataGrid.DataGrid}
     */
    masterGrid(heapSnapshotView) {
        return heapSnapshotView._constructorsDataGrid;
    }

    /**
     * @override
     * @return {boolean}
     */
    supportsSearch() {
        return true;
    }
};

/**
 * @unrestricted
 */
Profiler.HeapSnapshotView.ObjectListPerspective = class extends Profiler.HeapSnapshotView.Perspective {
    constructor() {
        super(Common.UIString('ObjectList'));
    }

    /**
     * @override
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     */
    activate(heapSnapshotView) {

        heapSnapshotView._splitWidget.setMainWidget(heapSnapshotView._objectListWidget);
        heapSnapshotView._splitWidget.setSidebarWidget(heapSnapshotView._objectDetailsView);
        heapSnapshotView._splitWidget.show(heapSnapshotView._searchableView.element);
        heapSnapshotView._searchObjectByID.setVisible(true);

        // heapSnapshotView._classNameFilter.setVisible(true);
        if (!heapSnapshotView._trackingOverviewGrid)
            return;
        heapSnapshotView._trackingOverviewGrid.show(
            heapSnapshotView._searchableView.element, heapSnapshotView._splitWidget.element);
        heapSnapshotView._trackingOverviewGrid.update();
        heapSnapshotView._trackingOverviewGrid._updateGrid();
    }

    /**
     * @override
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     * @return {?DataGrid.DataGrid}
     */
    masterGrid(heapSnapshotView) {
        return heapSnapshotView._objectListDataGrid;
    }

    /**
     * @override
     * @return {boolean}
     */
    supportsSearch() {
        return true;
    }
};

/**
 * @unrestricted
 */
Profiler.HeapSnapshotView.ComparisonPerspective = class extends Profiler.HeapSnapshotView.Perspective {
    constructor() {
        super(Common.UIString('Comparison'));
    }

    /**
     * @override
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     */
    activate(heapSnapshotView) {
        heapSnapshotView._splitWidget.setMainWidget(heapSnapshotView._diffWidget);
        heapSnapshotView._splitWidget.setSidebarWidget(heapSnapshotView._objectDetailsView);
        heapSnapshotView._splitWidget.show(heapSnapshotView._searchableView.element);
        heapSnapshotView._baseSelect.setVisible(true);
        heapSnapshotView._classNameFilter.setVisible(true);
    }

    /**
     * @override
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     * @return {?DataGrid.DataGrid}
     */
    masterGrid(heapSnapshotView) {
        return heapSnapshotView._diffDataGrid;
    }

    /**
     * @override
     * @return {boolean}
     */
    supportsSearch() {
        return true;
    }
};

/**
 * @unrestricted
 */
Profiler.HeapSnapshotView.ContainmentPerspective = class extends Profiler.HeapSnapshotView.Perspective {
    constructor() {
        super(Common.UIString('Containment'));
    }

    /**
     * @override
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     */
    activate(heapSnapshotView) {
        heapSnapshotView._searchObjectByID.setVisible(true);
        heapSnapshotView._splitWidget.setMainWidget(heapSnapshotView._containmentWidget);
        heapSnapshotView._splitWidget.setSidebarWidget(heapSnapshotView._objectDetailsView);
        heapSnapshotView._splitWidget.show(heapSnapshotView._searchableView.element);
    }

    /**
     * @override
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     * @return {?DataGrid.DataGrid}
     */
    masterGrid(heapSnapshotView) {
        return heapSnapshotView._containmentDataGrid;
    }
};

/**
 * @unrestricted
 */
Profiler.HeapSnapshotView.AllocationPerspective = class extends Profiler.HeapSnapshotView.Perspective {
    constructor() {
        super(Common.UIString('Allocation'));
        this._allocationSplitWidget = new UI.SplitWidget(false, true, 'heapSnapshotAllocationSplitViewState', 200, 200);
        this._allocationSplitWidget.setSidebarWidget(new UI.VBox());
    }

    /**
     * @override
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     */
    activate(heapSnapshotView) {
        this._allocationSplitWidget.setMainWidget(heapSnapshotView._allocationWidget);
        heapSnapshotView._splitWidget.setMainWidget(heapSnapshotView._constructorsWidget);
        heapSnapshotView._splitWidget.setSidebarWidget(heapSnapshotView._objectDetailsView);

        var allocatedObjectsView = new UI.VBox();
        var resizer = createElementWithClass('div', 'heap-snapshot-view-resizer');
        var title = resizer.createChild('div', 'title').createChild('span');
        title.textContent = Common.UIString('Live objects');
        this._allocationSplitWidget.hideDefaultResizer();
        this._allocationSplitWidget.installResizer(resizer);
        allocatedObjectsView.element.appendChild(resizer);
        heapSnapshotView._splitWidget.show(allocatedObjectsView.element);
        this._allocationSplitWidget.setSidebarWidget(allocatedObjectsView);

        this._allocationSplitWidget.show(heapSnapshotView._searchableView.element);

        heapSnapshotView._constructorsDataGrid.clear();
        var selectedNode = heapSnapshotView._allocationDataGrid.selectedNode;
        if (selectedNode)
            heapSnapshotView._constructorsDataGrid.setAllocationNodeId(selectedNode.allocationNodeId());
    }

    /**
     * @override
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     */
    deactivate(heapSnapshotView) {
        this._allocationSplitWidget.detach();
        super.deactivate(heapSnapshotView);
    }

    /**
     * @override
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     * @return {?DataGrid.DataGrid}
     */
    masterGrid(heapSnapshotView) {
        return heapSnapshotView._allocationDataGrid;
    }
};
Profiler.HeapSnapshotView.ReportPerspective = class extends Profiler.HeapSnapshotView.Perspective {
    constructor() {
      super(Common.UIString('ReportView'));
    }

    /**
     * @override
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     */
    activate(heapSnapshotView) {
        heapSnapshotView._splitWidget.detach();
        heapSnapshotView._splitWidget.detachChildWidgets();
        heapSnapshotView._reportView.show(heapSnapshotView._searchableView.element);
    }

    /**
     * @override
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     * @return {?DataGrid.DataGrid}
     */
    masterGrid(heapSnapshotView) {
        return null;
    }
}
/**
 * @unrestricted
 */
Profiler.HeapSnapshotView.StatisticsPerspective = class extends Profiler.HeapSnapshotView.Perspective {
    constructor() {
        super(Common.UIString('Statistics'));
    }

    /**
     * @override
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     */
    activate(heapSnapshotView) {
        heapSnapshotView._statisticsView.show(heapSnapshotView._searchableView.element);
    }

    /**
     * @override
     * @param {!Profiler.HeapSnapshotView} heapSnapshotView
     * @return {?DataGrid.DataGrid}
     */
    masterGrid(heapSnapshotView) {
        return null;
    }
};

/**
 * @implements {SDK.SDKModelObserver<!SDK.HeapProfilerModel>}
 * @unrestricted
 */
Profiler.HeapSnapshotProfileType = class extends Profiler.ProfileType {
    /**
     * @param {string=} id
     * @param {string=} title
     */
    constructor(id, title) {
        super(id || Profiler.HeapSnapshotProfileType.TypeId, title || Common.UIString('Take heap snapshot'));
        SDK.targetManager.observeModels(SDK.HeapProfilerModel, this);
        SDK.targetManager.addModelListener(
            SDK.HeapProfilerModel, SDK.HeapProfilerModel.Events.ResetProfiles, this._resetProfiles, this);
        SDK.targetManager.addModelListener(
            SDK.HeapProfilerModel, SDK.HeapProfilerModel.Events.AddHeapSnapshotChunk, this._addHeapSnapshotChunk, this);
        SDK.targetManager.addModelListener(
            SDK.HeapProfilerModel, SDK.HeapProfilerModel.Events.ReportHeapSnapshotProgress,
            this._reportHeapSnapshotProgress, this);
    }

    /**
     * @override
     * @param {!SDK.HeapProfilerModel} heapProfilerModel
     */
    modelAdded(heapProfilerModel) {
        heapProfilerModel.enable();
    }

    /**
     * @override
     * @param {!SDK.HeapProfilerModel} heapProfilerModel
     */
    modelRemoved(heapProfilerModel) {
    }

    /**
     * @override
     * @return {!Array<!Profiler.HeapProfileHeader>}
     */
    getProfiles() {
        return /** @type {!Array<!Profiler.HeapProfileHeader>} */ (super.getProfiles());
    }

    /**
     * @override
     * @return {string}
     */
    fileExtension() {
        return '.heapsnapshot';
    }

    get buttonTooltip() {
        return Common.UIString('Take heap snapshot');
    }

    /**
     * @override
     * @return {boolean}
     */
    isInstantProfile() {
        return true;
    }

    /**
     * @override
     * @return {boolean}
     */
    buttonClicked() {
        this._takeHeapSnapshot();
        Host.userMetrics.actionTaken(Host.UserMetrics.Action.ProfilesHeapProfileTaken);
        return false;
    }

    get treeItemTitle() {
        return Common.UIString('HEAP SNAPSHOTS');
    }

    get description() {
        return Common.UIString(
            'Heap snapshot profiles show memory distribution among your page\'s JavaScript objects and related DOM nodes.');
    }

    /**
     * @override
     * @param {string} title
     * @return {!Profiler.ProfileHeader}
     */
    createProfileLoadedFromFile(title) {
        return new Profiler.HeapProfileHeader(null, this, title);
    }

    async _takeHeapSnapshot() {
        if (this.profileBeingRecorded())
            return;
        var heapProfilerModel = UI.context.flavor(SDK.HeapProfilerModel);
        if (!heapProfilerModel)
            return;

        var profile = new Profiler.HeapProfileHeader(heapProfilerModel, this);
        this.setProfileBeingRecorded(profile);
        this.addProfile(profile);
        profile.updateStatus(Common.UIString('Snapshotting\u2026'));

        await heapProfilerModel.takeHeapSnapshot(true);
        // ------------ ASYNC ------------
        profile = this.profileBeingRecorded();
        profile.title = Common.UIString('Snapshot %d', profile.uid);
        profile._finishLoad();
        this.setProfileBeingRecorded(null);
        this.dispatchEventToListeners(Profiler.ProfileType.Events.ProfileComplete, profile);
    }

    /**
     * @param {!Common.Event} event
     */
    _addHeapSnapshotChunk(event) {
        if (!this.profileBeingRecorded())
            return;
        var chunk = /** @type {string} */ (event.data);
        this.profileBeingRecorded().transferChunk(chunk);
    }

    /**
     * @param {!Common.Event} event
     */
    _reportHeapSnapshotProgress(event) {
        var profile = this.profileBeingRecorded();
        if (!profile)
            return;
        var data = /** @type {{done: number, total: number, finished: boolean}} */ (event.data);
        profile.updateStatus(Common.UIString('%.0f%%', (data.done / data.total) * 100), true);
        if (data.finished)
            profile._prepareToLoad();
    }

    /**
     * @param {!Common.Event} event
     */
    _resetProfiles(event) {
        var heapProfilerModel = /** @type {!SDK.HeapProfilerModel} */ (event.data);
        for (var profile of this.getProfiles()) {
            if (profile._heapProfilerModel === heapProfilerModel)
                this.removeProfile(profile);
        }
    }

    _snapshotReceived(profile) {
        if (this.profileBeingRecorded() === profile)
            this.setProfileBeingRecorded(null);
        this.dispatchEventToListeners(Profiler.HeapSnapshotProfileType.SnapshotReceived, profile);
    }
};

Profiler.HeapSnapshotProfileType.TypeId = 'HEAP';
Profiler.HeapSnapshotProfileType.SnapshotReceived = 'SnapshotReceived';

/**
 * @unrestricted
 */
Profiler.TrackingHeapSnapshotProfileType = class extends Profiler.HeapSnapshotProfileType {
    constructor() {
        super(Profiler.TrackingHeapSnapshotProfileType.TypeId, Common.UIString('Record allocation timeline'));
    }

    /**
     * @override
     * @param {!SDK.HeapProfilerModel} heapProfilerModel
     */
    modelAdded(heapProfilerModel) {
        super.modelAdded(heapProfilerModel);
        heapProfilerModel.addEventListener(SDK.HeapProfilerModel.Events.HeapStatsUpdate, this._heapStatsUpdate, this);
        heapProfilerModel.addEventListener(SDK.HeapProfilerModel.Events.LastSeenObjectId, this._lastSeenObjectId, this);
    }

    /**
     * @override
     * @param {!SDK.HeapProfilerModel} heapProfilerModel
     */
    modelRemoved(heapProfilerModel) {
        super.modelRemoved(heapProfilerModel);
        heapProfilerModel.removeEventListener(SDK.HeapProfilerModel.Events.HeapStatsUpdate, this._heapStatsUpdate, this);
        heapProfilerModel.removeEventListener(SDK.HeapProfilerModel.Events.LastSeenObjectId, this._lastSeenObjectId, this);
    }

    /**
     * @param {!Common.Event} event
     */
    _heapStatsUpdate(event) {
        if (!this._profileSamples)
            return;
        var samples = /** @type {!Array.<number>} */ (event.data);
        var index;
        for (var i = 0; i < samples.length; i += 3) {
            index = samples[i];
            var size = samples[i + 2];
            this._profileSamples.sizes[index] = size;
            if (!this._profileSamples.max[index])
                this._profileSamples.max[index] = size;
        }
    }

    /**
     * @param {!Common.Event} event
     */
    _lastSeenObjectId(event) {
        var profileSamples = this._profileSamples;
        if (!profileSamples)
            return;
        var data = /** @type {{lastSeenObjectId: number, timestamp: number}} */ (event.data);
        var currentIndex = Math.max(profileSamples.ids.length, profileSamples.max.length - 1);
        profileSamples.ids[currentIndex] = data.lastSeenObjectId;
        if (!profileSamples.max[currentIndex]) {
            profileSamples.max[currentIndex] = 0;
            profileSamples.sizes[currentIndex] = 0;
        }
        profileSamples.timestamps[currentIndex] = data.timestamp;
        if (profileSamples.totalTime < data.timestamp - profileSamples.timestamps[0])
            profileSamples.totalTime *= 2;
        this.dispatchEventToListeners(Profiler.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._profileSamples);
        this.profileBeingRecorded().updateStatus(null, true);
    }

    /**
     * @override
     * @return {boolean}
     */
    hasTemporaryView() {
        return true;
    }

    get buttonTooltip() {
        return this._recording ? Common.UIString('Stop recording heap profile') :
            Common.UIString('Start recording heap profile');
    }

    /**
     * @override
     * @return {boolean}
     */
    isInstantProfile() {
        return false;
    }

    /**
     * @override
     * @return {boolean}
     */
    buttonClicked() {
        return this._toggleRecording();
    }

    _startRecordingProfile() {
        if (this.profileBeingRecorded())
            return;
        var heapProfilerModel = this._addNewProfile();
        if (!heapProfilerModel)
            return;
        var recordAllocationStacks = Common.moduleSetting('recordAllocationStacks').get();
        heapProfilerModel.startTrackingHeapObjects(recordAllocationStacks);
    }

    /**
     * @return {?SDK.HeapProfilerModel}
     */
    _addNewProfile() {
        var heapProfilerModel = UI.context.flavor(SDK.HeapProfilerModel);
        if (!heapProfilerModel)
            return null;
        this.setProfileBeingRecorded(new Profiler.HeapProfileHeader(heapProfilerModel, this, undefined));
        this._profileSamples = new Profiler.TrackingHeapSnapshotProfileType.Samples();
        this.profileBeingRecorded()._profileSamples = this._profileSamples;
        this._recording = true;
        this.addProfile(/** @type {!Profiler.ProfileHeader} */(this.profileBeingRecorded()));
        this.profileBeingRecorded().updateStatus(Common.UIString('Recording\u2026'));
        this.dispatchEventToListeners(Profiler.TrackingHeapSnapshotProfileType.TrackingStarted);
        return heapProfilerModel;
    }

    async _stopRecordingProfile() {
        this.profileBeingRecorded().updateStatus(Common.UIString('Snapshotting\u2026'));
        var stopPromise = this.profileBeingRecorded()._heapProfilerModel.stopTrackingHeapObjects(true);
        this._recording = false;
        this.dispatchEventToListeners(Profiler.TrackingHeapSnapshotProfileType.TrackingStopped);
        await stopPromise;
        // ------------ ASYNC ------------
        var profile = this.profileBeingRecorded();
        if (!profile)
            return;
        profile._finishLoad();
        this._profileSamples = null;
        this.setProfileBeingRecorded(null);
        this.dispatchEventToListeners(Profiler.ProfileType.Events.ProfileComplete, profile);
    }

    _toggleRecording() {
        if (this._recording)
            this._stopRecordingProfile();
        else
            this._startRecordingProfile();
        return this._recording;
    }

    /**
     * @override
     * @return {string}
     */
    fileExtension() {
        return '.heaptimeline';
    }

    get treeItemTitle() {
        return Common.UIString('ALLOCATION TIMELINES');
    }

    get description() {
        return Common.UIString(
            'Allocation timelines show memory allocations from your heap over time. Use this profile type to isolate memory leaks.');
    }

    /**
     * @override
     * @param {!Common.Event} event
     */
    _resetProfiles(event) {
        var wasRecording = this._recording;
        // Clear current profile to avoid stopping backend.
        this.setProfileBeingRecorded(null);
        super._resetProfiles(event);
        this._profileSamples = null;
        if (wasRecording)
            this._addNewProfile();
    }

    /**
     * @override
     */
    profileBeingRecordedRemoved() {
        this._stopRecordingProfile();
        this._profileSamples = null;
    }
};

Profiler.TrackingHeapSnapshotProfileType.TypeId = 'HEAP-RECORD';

Profiler.TrackingHeapSnapshotProfileType.HeapStatsUpdate = 'HeapStatsUpdate';
Profiler.TrackingHeapSnapshotProfileType.TrackingStarted = 'TrackingStarted';
Profiler.TrackingHeapSnapshotProfileType.TrackingStopped = 'TrackingStopped';

/**
 * @unrestricted
 */
Profiler.TrackingHeapSnapshotProfileType.Samples = class {
    constructor() {
        /** @type {!Array.<number>} */
        this.sizes = [];
        /** @type {!Array.<number>} */
        this.ids = [];
        /** @type {!Array.<number>} */
        this.timestamps = [];
        /** @type {!Array.<number>} */
        this.max = [];
        /** @type {number} */
        this.totalTime = 30000;
    }
};

/**
 * @unrestricted
 */
Profiler.HeapProfileHeader = class extends Profiler.ProfileHeader {
    /**
     * @param {?SDK.HeapProfilerModel} heapProfilerModel
     * @param {!Profiler.HeapSnapshotProfileType} type
     * @param {string=} title
     */
    constructor(heapProfilerModel, type, title) {
        super(type, title || Common.UIString('Snapshot %d', type.nextProfileUid()));
        this._heapProfilerModel = heapProfilerModel;
        this.maxJSObjectId = -1;
        /** @type {?Profiler.HeapSnapshotWorkerProxy} */
        this._workerProxy = null;
        /** @type {?Common.OutputStream} */
        this._receiver = null;
        /** @type {?Profiler.HeapSnapshotProxy} */
        this._snapshotProxy = null;
        /** @type {!Promise<!Profiler.HeapSnapshotProxy>} */
        this._loadPromise = new Promise(resolve => this._fulfillLoad = resolve);
        this._totalNumberOfChunks = 0;
        this._bufferedWriter = null;
        /** @type {?Bindings.TempFile} */
        this._tempFile = null;
    }

    /**
     * @return {?SDK.HeapProfilerModel}
     */
    heapProfilerModel() {
        return this._heapProfilerModel;
    }

    /**
     * @override
     * @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate
     * @return {!Profiler.ProfileSidebarTreeElement}
     */
    createSidebarTreeElement(dataDisplayDelegate) {
        return new Profiler.ProfileSidebarTreeElement(dataDisplayDelegate, this, 'heap-snapshot-sidebar-tree-item');
    }

    /**
     * @override
     * @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate
     * @return {!Profiler.HeapSnapshotView}
     */
    createView(dataDisplayDelegate) {
        return new Profiler.HeapSnapshotView(dataDisplayDelegate, this);
    }

    _prepareToLoad() {
        console.assert(!this._receiver, 'Already loading');
        this._setupWorker();
        this.updateStatus(Common.UIString('Loading\u2026'), true);
    }

    _finishLoad() {
        if (!this._wasDisposed)
            this._receiver.close();
        if (!this._bufferedWriter)
            return;
        this._didWriteToTempFile(this._bufferedWriter);
    }

    /**
     * @param {!Bindings.TempFile} tempFile
     */
    _didWriteToTempFile(tempFile) {
        if (this._wasDisposed) {
            if (tempFile)
                tempFile.remove();
            return;
        }
        this._tempFile = tempFile;
        if (!tempFile)
            this._failedToCreateTempFile = true;
        if (this._onTempFileReady) {
            this._onTempFileReady();
            this._onTempFileReady = null;
        }
    }

    _setupWorker() {
        /**
         * @this {Profiler.HeapProfileHeader}
         */
        function setProfileWait(event) {
            this.updateStatus(null, event.data);
        }
        console.assert(!this._workerProxy, 'HeapSnapshotWorkerProxy already exists');
        this._workerProxy = new Profiler.HeapSnapshotWorkerProxy(this._handleWorkerEvent.bind(this));
        this._workerProxy.addEventListener(Profiler.HeapSnapshotWorkerProxy.Events.Wait, setProfileWait, this);
        this._receiver = this._workerProxy.createLoader(this.uid, this._snapshotReceived.bind(this));
    }

    /**
     * @param {string} eventName
     * @param {*} data
     */
    _handleWorkerEvent(eventName, data) {
        if (HeapSnapshotModel.HeapSnapshotProgressEvent.BrokenSnapshot === eventName) {
            var error = /** @type {string} */ (data);
            Common.console.error(error);
            return;
        }

        if (HeapSnapshotModel.HeapSnapshotProgressEvent.Update !== eventName)
            return;
        var subtitle = /** @type {string} */ (data);
        this.updateStatus(subtitle);
    }

    /**
     * @override
     */
    dispose() {
        if (this._workerProxy)
            this._workerProxy.dispose();
        this.removeTempFile();
        this._wasDisposed = true;
    }

    _didCompleteSnapshotTransfer() {
        if (!this._snapshotProxy)
            return;
        this.updateStatus(Number.bytesToString(this._snapshotProxy.totalSize), false);
    }

    /**
     * @param {string} chunk
     */
    transferChunk(chunk) {
        if (!this._bufferedWriter)
            this._bufferedWriter = new Bindings.TempFile();
        this._bufferedWriter.write([chunk]);

        ++this._totalNumberOfChunks;
        this._receiver.write(chunk);
    }

    _snapshotReceived(snapshotProxy) {
        if (this._wasDisposed)
            return;
        this._receiver = null;
        this._snapshotProxy = snapshotProxy;
        this.maxJSObjectId = snapshotProxy.maxJSObjectId();
        this._didCompleteSnapshotTransfer();
        this._workerProxy.startCheckingForLongRunningCalls();
        this.notifySnapshotReceived();
    }

    notifySnapshotReceived() {
        this._fulfillLoad(this._snapshotProxy);
        this.profileType()._snapshotReceived(this);
        if (this.canSaveToFile())
            this.dispatchEventToListeners(Profiler.ProfileHeader.Events.ProfileReceived);
    }

    /**
     * @override
     * @return {boolean}
     */
    canSaveToFile() {
        return !this.fromFile() && !!this._snapshotProxy;
    }

    /**
     * @override
     */
    saveToFile() {
        var fileOutputStream = new Bindings.FileOutputStream();
        this._fileName = this._fileName || 'Heap-' + new Date().toISO8601Compact() + this.profileType().fileExtension();
        fileOutputStream.open(this._fileName).then(onOpen.bind(this));

        /**
         * @param {boolean} accepted
         * @this {Profiler.HeapProfileHeader}
         */
        async function onOpen(accepted) {
            if (!accepted)
                return;
            if (this._failedToCreateTempFile) {
                Common.console.error('Failed to open temp file with heap snapshot');
                fileOutputStream.close();
                return;
            }
            if (this._tempFile) {
                var error = await this._tempFile.copyToOutputStream(fileOutputStream, this._onChunkTransferred.bind(this));
                if (error)
                    Common.console.error('Failed to read heap snapshot from temp file: ' + error.message);
                this._didCompleteSnapshotTransfer();
                return;
            }
            this._onTempFileReady = onOpen.bind(this, accepted);
            this._updateSaveProgress(0, 1);
        }
    }

    /**
     * @param {!Bindings.ChunkedReader} reader
     */
    _onChunkTransferred(reader) {
        this._updateSaveProgress(reader.loadedSize(), reader.fileSize());
    }

    /**
     * @param {number} value
     * @param {number} total
     */
    _updateSaveProgress(value, total) {
        var percentValue = ((total && value / total) * 100).toFixed(0);
        this.updateStatus(Common.UIString('Saving\u2026 %d%%', percentValue));
    }

    /**
     * @override
     * @param {!File} file
     * @return {!Promise<?Error>}
     */
    async loadFromFile(file) {
        this.updateStatus(Common.UIString('Loading\u2026'), true);
        this._setupWorker();
        var reader = new Bindings.ChunkedFileReader(file, 10000000);
        var success = await reader.read(/** @type {!Common.OutputStream} */(this._receiver));
        if (!success)
            this.updateStatus(reader.error().message);
        return success ? null : reader.error();
    }
};

/**
 * @unrestricted
 */
Profiler.HeapTrackingOverviewGrid = class extends UI.VBox {
    /**
     * @param {!Profiler.HeapProfileHeader} heapProfileHeader
     */
    constructor(heapProfileHeader) {
        super();
        this.element.id = 'heap-recording-view';
        this.element.classList.add('heap-tracking-overview');

        this._overviewContainer = this.element.createChild('div', 'heap-overview-container');
        this._overviewGrid = new PerfUI.OverviewGrid('heap-recording');
        this._overviewGrid.element.classList.add('fill');

        this._overviewCanvas = this._overviewContainer.createChild('canvas', 'heap-recording-overview-canvas');
        this._overviewContainer.appendChild(this._overviewGrid.element);
        this._overviewCalculator = new Profiler.HeapTrackingOverviewGrid.OverviewCalculator();
        this._overviewGrid.addEventListener(PerfUI.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);

        this._profileSamples = heapProfileHeader.fromFile() ? new Profiler.TrackingHeapSnapshotProfileType.Samples() :
            heapProfileHeader._profileSamples;
        this._profileType = heapProfileHeader.profileType();
        if (!heapProfileHeader.fromFile() && heapProfileHeader.profileType().profileBeingRecorded() === heapProfileHeader) {
            this._profileType.addEventListener(
                Profiler.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this);
            this._profileType.addEventListener(
                Profiler.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this);
        }
        this._windowLeft = 0.0;
        this._windowRight = 1.0;
        this._overviewGrid.setWindow(this._windowLeft, this._windowRight);
        this._yScale = new Profiler.HeapTrackingOverviewGrid.SmoothScale();
        this._xScale = new Profiler.HeapTrackingOverviewGrid.SmoothScale();
    }

    dispose() {
        this._onStopTracking();
    }

    _onStopTracking() {
        this._profileType.removeEventListener(
            Profiler.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this);
        this._profileType.removeEventListener(
            Profiler.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this);
    }

    /**
     * @param {!Common.Event} event
     */
    _onHeapStatsUpdate(event) {
        this._profileSamples = event.data;
        this._scheduleUpdate();
    }

    /**
     * @param {?HeapSnapshotModel.Samples} samples
     */
    _setSamples(samples) {
        if (!samples)
            return;
        console.assert(!this._profileSamples.timestamps.length, 'Should only call this method when loading from file.');
        console.assert(samples.timestamps.length);
        this._profileSamples = new Profiler.TrackingHeapSnapshotProfileType.Samples();
        this._profileSamples.sizes = samples.sizes;
        this._profileSamples.ids = samples.lastAssignedIds;
        this._profileSamples.timestamps = samples.timestamps;
        this._profileSamples.max = samples.sizes;
        this._profileSamples.totalTime = /** @type{number} */ (samples.timestamps.peekLast());
        this.update();
    }

    /**
     * @param {number} width
     * @param {number} height
     */
    _drawOverviewCanvas(width, height) {
        if (!this._profileSamples)
            return;
        var profileSamples = this._profileSamples;
        var sizes = profileSamples.sizes;
        var topSizes = profileSamples.max;
        var timestamps = profileSamples.timestamps;
        var startTime = timestamps[0];
        var endTime = timestamps[timestamps.length - 1];

        var scaleFactor = this._xScale.nextScale(width / profileSamples.totalTime);
        var maxSize = 0;
        /**
         * @param {!Array.<number>} sizes
         * @param {function(number, number):void} callback
         */
        function aggregateAndCall(sizes, callback) {
            var size = 0;
            var currentX = 0;
            for (var i = 1; i < timestamps.length; ++i) {
                var x = Math.floor((timestamps[i] - startTime) * scaleFactor);
                if (x !== currentX) {
                    if (size)
                        callback(currentX, size);
                    size = 0;
                    currentX = x;
                }
                size += sizes[i];
            }
            callback(currentX, size);
        }

        /**
         * @param {number} x
         * @param {number} size
         */
        function maxSizeCallback(x, size) {
            maxSize = Math.max(maxSize, size);
        }

        aggregateAndCall(sizes, maxSizeCallback);

        var yScaleFactor = this._yScale.nextScale(maxSize ? height / (maxSize * 1.1) : 0.0);

        this._overviewCanvas.width = width * window.devicePixelRatio;
        this._overviewCanvas.height = height * window.devicePixelRatio;
        this._overviewCanvas.style.width = width + 'px';
        this._overviewCanvas.style.height = height + 'px';

        var context = this._overviewCanvas.getContext('2d');
        context.scale(window.devicePixelRatio, window.devicePixelRatio);

        context.beginPath();
        context.lineWidth = 2;
        context.strokeStyle = 'rgba(192, 192, 192, 0.6)';
        var currentX = (endTime - startTime) * scaleFactor;
        context.moveTo(currentX, height - 1);
        context.lineTo(currentX, 0);
        context.stroke();
        context.closePath();

        var gridY;
        var gridValue;
        var gridLabelHeight = 14;
        if (yScaleFactor) {
            const maxGridValue = (height - gridLabelHeight) / yScaleFactor;
            // The round value calculation is a bit tricky, because
            // it has a form k*10^n*1024^m, where k=[1,5], n=[0..3], m is an integer,
            // e.g. a round value 10KB is 10240 bytes.
            gridValue = Math.pow(1024, Math.floor(Math.log(maxGridValue) / Math.log(1024)));
            gridValue *= Math.pow(10, Math.floor(Math.log(maxGridValue / gridValue) / Math.LN10));
            if (gridValue * 5 <= maxGridValue)
                gridValue *= 5;
            gridY = Math.round(height - gridValue * yScaleFactor - 0.5) + 0.5;
            context.beginPath();
            context.lineWidth = 1;
            context.strokeStyle = 'rgba(0, 0, 0, 0.2)';
            context.moveTo(0, gridY);
            context.lineTo(width, gridY);
            context.stroke();
            context.closePath();
        }

        /**
         * @param {number} x
         * @param {number} size
         */
        function drawBarCallback(x, size) {
            context.moveTo(x, height - 1);
            context.lineTo(x, Math.round(height - size * yScaleFactor - 1));
        }

        context.beginPath();
        context.lineWidth = 2;
        context.strokeStyle = 'rgba(192, 192, 192, 0.6)';
        aggregateAndCall(topSizes, drawBarCallback);
        context.stroke();
        context.closePath();

        context.beginPath();
        context.lineWidth = 2;
        context.strokeStyle = 'rgba(0, 0, 192, 0.8)';
        aggregateAndCall(sizes, drawBarCallback);
        context.stroke();
        context.closePath();

        if (gridValue) {
            var label = Number.bytesToString(gridValue);
            var labelPadding = 4;
            var labelX = 0;
            var labelY = gridY - 0.5;
            var labelWidth = 2 * labelPadding + context.measureText(label).width;
            context.beginPath();
            context.textBaseline = 'bottom';
            context.font = '10px ' + window.getComputedStyle(this.element, null).getPropertyValue('font-family');
            context.fillStyle = 'rgba(255, 255, 255, 0.75)';
            context.fillRect(labelX, labelY - gridLabelHeight, labelWidth, gridLabelHeight);
            context.fillStyle = 'rgb(64, 64, 64)';
            context.fillText(label, labelX + labelPadding, labelY);
            context.fill();
            context.closePath();
        }
    }

    /**
     * @override
     */
    onResize() {
        this._updateOverviewCanvas = true;
        this._scheduleUpdate();
    }

    _onWindowChanged() {
        if (!this._updateGridTimerId)
            this._updateGridTimerId = setTimeout(this._updateGrid.bind(this), 10);
    }

    _scheduleUpdate() {
        if (this._updateTimerId)
            return;
        this._updateTimerId = setTimeout(this.update.bind(this), 10);
    }

    _updateBoundaries() {
        this._windowLeft = this._overviewGrid.windowLeft();
        this._windowRight = this._overviewGrid.windowRight();
        this._windowWidth = this._windowRight - this._windowLeft;
    }

    update() {
        this._updateTimerId = null;
        if (!this.isShowing())
            return;
        this._updateBoundaries();
        this._overviewCalculator._updateBoundaries(this);
        this._overviewGrid.updateDividers(this._overviewCalculator);
        this._drawOverviewCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - 20);
    }

    _updateGrid() {
        this._updateGridTimerId = 0;
        this._updateBoundaries();
        var ids = this._profileSamples.ids;
        var timestamps = this._profileSamples.timestamps;
        var sizes = this._profileSamples.sizes;
        var startTime = timestamps[0];
        var totalTime = this._profileSamples.totalTime;
        var timeLeft = startTime + totalTime * this._windowLeft;
        var timeRight = startTime + totalTime * this._windowRight;
        var minId = 0;
        var maxId = ids[ids.length - 1] + 1;
        var size = 0;
        for (var i = 0; i < timestamps.length; ++i) {
            if (!timestamps[i])
                continue;
            if (timestamps[i] > timeRight)
                break;
            maxId = ids[i];
            if (timestamps[i] < timeLeft) {
                minId = ids[i];
                continue;
            }
            size += sizes[i];
        }

        this.dispatchEventToListeners(
            Profiler.HeapTrackingOverviewGrid.IdsRangeChanged, { minId: minId, maxId: maxId, size: size });
    }
};

Profiler.HeapTrackingOverviewGrid.IdsRangeChanged = Symbol('IdsRangeChanged');

Profiler.HeapTrackingOverviewGrid.SmoothScale = class {
    constructor() {
        this._lastUpdate = 0;
        this._currentScale = 0.0;
    }

    /**
     * @param {number} target
     * @return {number}
     */
    nextScale(target) {
        target = target || this._currentScale;
        if (this._currentScale) {
            var now = Date.now();
            var timeDeltaMs = now - this._lastUpdate;
            this._lastUpdate = now;
            var maxChangePerSec = 20;
            var maxChangePerDelta = Math.pow(maxChangePerSec, timeDeltaMs / 1000);
            var scaleChange = target / this._currentScale;
            this._currentScale *= Number.constrain(scaleChange, 1 / maxChangePerDelta, maxChangePerDelta);
        } else {
            this._currentScale = target;
        }
        return this._currentScale;
    }
};

/**
 * @implements {PerfUI.TimelineGrid.Calculator}
 * @unrestricted
 */
Profiler.HeapTrackingOverviewGrid.OverviewCalculator = class {
    /**
     * @param {!Profiler.HeapTrackingOverviewGrid} chart
     */
    _updateBoundaries(chart) {
        this._minimumBoundaries = 0;
        this._maximumBoundaries = chart._profileSamples.totalTime;
        this._xScaleFactor = chart._overviewContainer.clientWidth / this._maximumBoundaries;
    }

    /**
     * @override
     * @param {number} time
     * @return {number}
     */
    computePosition(time) {
        return (time - this._minimumBoundaries) * this._xScaleFactor;
    }

    /**
     * @override
     * @param {number} value
     * @param {number=} precision
     * @return {string}
     */
    formatValue(value, precision) {
        return Number.secondsToString(value / 1000, !!precision);
    }

    /**
     * @override
     * @return {number}
     */
    maximumBoundary() {
        return this._maximumBoundaries;
    }

    /**
     * @override
     * @return {number}
     */
    minimumBoundary() {
        return this._minimumBoundaries;
    }

    /**
     * @override
     * @return {number}
     */
    zeroTime() {
        return this._minimumBoundaries;
    }

    /**
     * @override
     * @return {number}
     */
    boundarySpan() {
        return this._maximumBoundaries - this._minimumBoundaries;
    }
};
Profiler.HeapSnapshotReportView = class extends UI.VBox {
    constructor(){
        super();
        this.element.classList.add("heap-snapshot-report-view");
        this.setMinimumSize(50,25);
        this.reportContainerElem = this.element.createChild("div", "heap-snapshot-report");
        this.trendBoxElem = this.reportContainerElem.createChild("div", "trend_box");
        this.recommendBoxElem = this.reportContainerElem.createChild("div", "recommend_box");
        this.trendTitleElem = this.trendBoxElem.createChild("div", "heap-report-trend-title");
        this.trendTitleElem.innerHTML = `<h2>一. Acount of Memory & Object</h2>`
        this.trendElement = this.trendBoxElem.createChild("div", "heap-report-trend");
        this.trendElement.id = "heap-trend-graph";
        this.trendInfoContainerElem = this.trendBoxElem.createChild("div", "heap-report-trend-info-container")
        this.trendInfoElem = this.trendInfoContainerElem.createChild("div", "heap-report-trend-info");

        this.leakTitleElem = this.recommendBoxElem.createChild("div", "heap-snapshot-leak-title");
        this.leakTitleElem.innerHTML = `<h2>二. Suspected</h2>`
        this.leakInfoElem = this.recommendBoxElem.createChild("div", "heap-snapshot-leak-info");
        this.leakInfoFirst = this.leakInfoElem.createChild("div", "heap-snapshot-leak-info-first")
        this.leakInfoSecond = this.leakInfoElem.createChild("div", "heap-snapshot-leak-info-second")
        this.leakInfoThird = this.leakInfoElem.createChild("div", "heap-snapshot-leak-info-third")

        this.leakInfoFirstTitle = this.leakInfoFirst.createChild("div", "title")
        this.leakInfoSecondTitle = this.leakInfoSecond.createChild("div", "title")
        this.leakInfoThirdTitle = this.leakInfoThird.createChild("div", "title")
        this.leakInfoFirstContent = this.leakInfoFirst.createChild("div", "content")
        this.leakInfoSecondContent = this.leakInfoSecond.createChild("div", "content")
        this.leakInfoThirdContent = this.leakInfoThird.createChild("div", "content")
    }

    setTrendData(summaryInfo, itemsInfo){
        this.trendInfo = summaryInfo;
        this.itemsInfo = itemsInfo;
    }

    setLeakItems(leakItems){
        this.leakItems = leakItems
    }

    setLeakClass(suspClasses){
        this.suspClasses = suspClasses;
    }

    setDiffData(DiffData){
        this.diffData = DiffData;
    }

    setDiffsArrayData(diffsArray){
        this.diffsArray = diffsArray;
    }

    getTrendInfoData(){
        return this.trendInfo;
    }

    getItemsData(){
        return this.itemsInfo;
    }

    getDiffsArrayData(){
        return this.diffsArray;
    }

    getLeakItems(){
        return this.leakItems;
    }

    getLeakClass(){
        return this.suspClasses
    }

    getDiffData(){
        return this.diffData;
    }

    init(){
        this._generateTrendInfo();
        this._generateLeakInfo();
    }

    _generateTrendInfo(){
        this._generateSummary();
        this._drawGraph();

    }

    _generateSummary(){
        var items = this.getItemsData();
        var len = items.length;
        var info = "";
        var sizeDelta = "";

        var classInfo = null;
        var trendInfo = this.getTrendInfoData();


        if(!trendInfo.isFilesCountEnough){
            info = `<span class="highlight">Summary</span>: Heap files  are too few to determine if there is a leak based on trend information. Please upload 4 or more heap files for better trend analysis.`;
        }else if(!trendInfo.isAlwaysUp && !trendInfo.isTailsUp){
            console.log(trendInfo)
            info = `<span class="highlight">Summary</span>：As show in bar chart, the memory did not show a clear upward trend and infer that there is less probability existing memory leak.`
        }
        else{
            classInfo = this._getClassInfo();

            sizeDelta = items[len-1].totalSize - items[trendInfo.breakIndex].totalSize;
            if(trendInfo.isAlwaysUp){
              if(classInfo.added_out_class_count > 0 && classInfo.outClassPercent> 0.1){
                var percent = (classInfo.outClassPercent*100).toFixed(2)+"%";

                info = `<span class="highlight">Summary:</span> As show in bar chart，the memory present an upward trend with a <span class="highlight">${trendInfo.isBigLeak?"large":"small"} </span> increment of <span class="highlight">${Math.round(sizeDelta/1024)} KB. </span>`
                info += `Meanwhile the number of added outer classes is <span class="highlight">${classInfo.added_out_class_count}</span>，
                                 and these classes account for <span class="highlight">${percent}( about ${(classInfo.added_out_class_mem/1024).toFixed(2)}KB)</span> of the memory increment.`
              }
              else{
                var inPercent = (classInfo.inClassPercent*100).toFixed(2)+"%";
                var outPercent = (classInfo.outClassPercent*100).toFixed(2)+"%";
                info = `<span class="highlight">Summary:</span> As show in bar chart, the memory presents a growth trend，however most of the memory increment(<span class="highlight">${inPercent}</span>) is from classes defined in v8.
                        The number of added outer classes is <span class="highlight">${classInfo.added_out_class_count}</span>， which account for <span class="highlight">${outPercent}(${(classInfo.added_out_class_mem/1024).toFixed(2)}KB)</span> of the memeory increment. Therefor，there is a <span class="highlight">small</span> amount of memory leak`;
              }

            }else{
              if(trendInfo.isTailsUp){
                if(classInfo.added_out_class_count > 0 && classInfo.outClassPercent> 0.1){
                  var percent = (classInfo.outClassPercent*100).toFixed(2)+"%";
                  info = `<span class="highlight">Summary:</span> As show in bar chart, the memory begins to show the <span class="highlight">growth trend</span> from the <span class="highlight">${trendInfo.breakIndex+1}</span> snapshot.Duraing this period it produces a total of <span class="highlight">${Math.round(sizeDelta/1024)} KB</span> memory increment, which is relatively 
                  <span class="highlight">${trendInfo.isBigLeak?"large":"small"}.</span> `;
                  info += `Meanwhile the number of added outer classes is <span class="highlight">${classInfo.added_out_class_count}</span>， 
                   which account for <span class="highlight">${percent}(about ${(classInfo.added_out_class_mem/1024).toFixed(2)}KB)</span> of the memory increment.`;
                }else{
                  var inPercent = (classInfo.inClassPercent*100).toFixed(2)+"%";
                  var outPercent = (classInfo.outClassPercent*100).toFixed(2)+"%";
                  info = `<span class="highlight">Summary:</span> the memory presents an growth trend from the <span class="highlight">${trendInfo.breakIndex+1}</span> snapshot, however most of the memory increment <span class="highlight">(about ${inPercent}%)</span> are from classes defined by v8，
                        the number of added outer classes is <span class="highlight">${classInfo.added_out_class_count}</span>，which account for <span class="highlight">${outPercent}(about ${(classInfo.added_out_class_mem/1024).toFixed(2)}KB)</span> of the memory increment. Therefor，there is a <span class="highlight">small</span> amount of memory leak`;
                }

              }else
                info = `<span class="highlight">Summary:</span> As show in bar chart, the memory did not show a clear upward trend and infer that there is <span class="highlight">no memory leak</span> in large probablity.`
            }
        }
        this.trendInfoElem.innerHTML = info;
    }

    _generateLeakInfo(){
        var leakItems = this.getLeakItems();
        var firstStr="", secondStr="", thirdStr = "";
        var tempItems = leakItems.map(item=>item);
        var leakClasses = this.getLeakClass();

        var trendInfo = this.getTrendInfoData();
        tempItems.sort(function(a,b){return b.retainedSize-a.retainedSize})

        tempItems.slice(0,5).forEach(function(item){
          var name = item.name?item.name.slice(0,20):item.f_name;
          firstStr += `<li><span style="background-color:#eed263; margin-top: 5px; display:inline-block">${item.className}@${name}@${item.id}@${Math.round((item.retainedSize) / 1024)} (KB)</span></li>`;
        })

        this.leakInfoFirstTitle.innerHTML = `1. The following objects accumulate more memory </br>(Format: [class] @ [name] @ [id] @ [amount of memory]): `;
        this.leakInfoFirstContent.innerHTML = `<ul>${firstStr}</ul>`;

        var groupInfo = {},groupArrs=[];
        for(var i = 0;i<tempItems.length;i++){
          var item = tempItems[i];
          if(groupInfo[item.markByGroup]) continue;
          else {
            groupInfo[item.markByGroup] = 1;
            groupArrs.push({
              name:item.name?item.name:item.f_name,
              className: item.className,
              groupCount:item.groupCount
            });
          }
        }
        groupArrs.sort(function(a,b){return b.groupCount-a.groupCount});
        groupArrs.slice(0,5).forEach(function(item){
          secondStr += `<li><span style="background-color:#eed263; margin-top: 5px; display:inline-block">${item.className}@${item.name.slice(0,35)}@${item.groupCount}</span></li>`;
        })
    
        this.leakInfoSecondTitle.innerHTML = `2. The following objects have higher repetitions </br>  (Format：[class] @ [name] @ [number of reptitions]):`;
        this.leakInfoSecondContent.innerHTML = `<ul>${secondStr}</ul>`
        
        if(leakClasses){
          for(var i = 0;i<leakClasses.length;i++){
            var item = leakClasses[i];
            thirdStr += `<li><span style="background-color:#eed263; margin-top: 5px; display:inline-block">${item.className} @ ${item.countDelta} @ ${((item.sizeDelta)/1024).toFixed(2)} (KB)</span></li>`;
          }

          this.leakInfoThirdTitle.innerHTML = `3. The objects in following classes keep growing </br>  (Format：[class] @ [number of added objects] @ [added amount of memory]):`;
          this.leakInfoThirdContent.innerHTML = `<ul>${thirdStr}</ul>`;
        }

    }

    _getClassInfo(){
        var result = {
            inClassPercent:0,
            outClassPercent:0,
            added_in_class_mem:0,
            added_out_class_mem:0,
            added_in_class_count:0,
            added_out_class_count:0,
            isOnlySingle:false
        };
        var susp_classes = [];
        var v8Class = ["(system)", "(compiled code)", "(array)", "(sliced string)", "(concatenated string)", "(number)"];
        var diffData = this.getDiffData();
        
        if(!diffData){
            result.isOnlySingle = true;
            return result;
        }

        if(this.getDiffsArrayData()){
            var diffsArray = this.getDiffsArrayData();
            var keys = Object.keys(diffsArray[0]);
            for(let key of keys){
                let flag = true
                for(let i = 0; i<diffsArray.length-1;i++){
                    if(v8Class.includes(key) || !(key in diffsArray[i+1])){
                        flag = false;
                        break;
                    }

                    if(diffsArray[i][key].countDelta <= diffsArray[i+1][key].countDelta){
                        flag = false;
                        break;
                    }
                }
                if(flag) susp_classes.push({
                    className: key,
                    countDelta: diffsArray[0][key].countDelta,
                    sizeDelta: diffsArray[0][key].sizeDelta
                })
            }
            susp_classes.sort((a,b)=>b.sizeDelta-a.sizeDelta) // 排序
            this.setLeakClass(susp_classes);
        }


        

        for(var name in diffData){
          if(v8Class.includes(name)){
            if(diffData[name].countDelta > 0)
              result.added_in_class_count++;
            if(diffData[name].sizeDelta > 0)
              result.added_in_class_mem += diffData[name].sizeDelta
          }else{
            if(diffData[name].countDelta > 0 && name!="(string)")
              result.added_out_class_count++;
            if(diffData[name].sizeDelta > 0)
              result.added_out_class_mem += diffData[name].sizeDelta
          }
        }
        result.inClassPercent = result.added_in_class_mem/(result.added_in_class_mem+result.added_out_class_mem)
        result.outClassPercent = 1-result.inClassPercent;
        return result;
    }

    _drawGraph(){
        var myEcharts = echarts.init(this.trendElement);
        myEcharts.setOption(generateOption(this.getItemsData()));

        function generateOption(items){
          var colors = ['#5793f3', '#d14a61', '#675bba'];
          var types = ["totalSize", "nodeCount"];
          var labels = ["Memory Acount(KB)", "Object Acount"];
          var xLabels = items.map(function(item,i){return "Report_"+i;})
          var data = [];
          types.forEach(function(t, i){
            var tmp = {
              name: t,
              label: labels[i],
              values: t=="totalSize"?items.map(function(item){return item[t]/1024}):items.map(function(item){return item[t]})
            }
            tmp.min = Math.min(...tmp.values);
            tmp.max = Math.max(...tmp.values);
            data.push(tmp)
          })


          var option = {
            color: colors,

            tooltip: {
              trigger: 'axis',
              axisPointer: {
                type: 'cross'
              }
            },
            grid: {
              right: '20%'
            },
            toolbox: {
              feature: {
                dataView: {show: true, readOnly: false},
                restore: {show: true},
                saveAsImage: {show: true}
              },
              right:20
            },
            legend: {
              data:labels
            },
            grid: {
              left: '10%',
              right: '10%',
              top: 60,
              bottom:60

            },
            xAxis: [
              {
                type: 'category',
                axisTick: {
                  alignWithLabel: true
                },
                // boundaryGap: false,
                data: xLabels
              }
            ],
            yAxis: [
              {
                type: 'value',
                name: labels[0],
                min: Math.round(data[0].min*0.9),
                max: Math.round(data[0].max*1.1),
                position: 'left',
                axisLine: {
                  lineStyle: {
                    color: colors[0]
                  }
                },
                axisLabel: {
                  formatter: '{value}'
                },
                splitLine:{
                  lineStyle:{
                    opacity:0.5
                  }
                }
              },
              {
                type: 'value',
                name: labels[1],
                min: Math.round(data[1].min*0.9),
                max: Math.round(data[1].max*1.1),
                position: 'right',
                axisLine: {
                  lineStyle: {
                    color: colors[1]
                  }
                },
                axisLabel: {
                  formatter: '{value}'
                },
                splitLine:{
                  lineStyle:{
                    opacity:0.5
                  }
                }
              }
            ],
            series: [
              {
                name: labels[0],
                type:'bar',
                data: data[0].values
              },
              {
                name:labels[1],
                type:'bar',
                yAxisIndex: 1,
                data: data[1].values
              }
            ]
          };

          return option;
        }
    }

}
/**
 * @unrestricted
 */
Profiler.HeapSnapshotStatisticsView = class extends UI.VBox {
    constructor() {
        super();
        this.element.classList.add('heap-snapshot-statistics-view');
        this._pieChart = new PerfUI.PieChart(150, Profiler.HeapSnapshotStatisticsView._valueFormatter, true);
        this._pieChart.element.classList.add('heap-snapshot-stats-pie-chart');
        this.element.appendChild(this._pieChart.element);
        this._labels = this.element.createChild('div', 'heap-snapshot-stats-legend');
    }

    /**
     * @param {number} value
     * @return {string}
     */
    static _valueFormatter(value) {
        return Common.UIString('%s KB', Number.withThousandsSeparator(Math.round(value / 1024)));
    }

    /**
     * @param {number} value
     */
    setTotal(value) {
        this._pieChart.setTotal(value);
    }

    /**
     * @param {number} value
     * @param {string} name
     * @param {string=} color
     */
    addRecord(value, name, color) {
        if (color)
            this._pieChart.addSlice(value, color);

        var node = this._labels.createChild('div');
        var swatchDiv = node.createChild('div', 'heap-snapshot-stats-swatch');
        var nameDiv = node.createChild('div', 'heap-snapshot-stats-name');
        var sizeDiv = node.createChild('div', 'heap-snapshot-stats-size');
        if (color)
            swatchDiv.style.backgroundColor = color;
        else
            swatchDiv.classList.add('heap-snapshot-stats-empty-swatch');
        nameDiv.textContent = name;
        sizeDiv.textContent = Profiler.HeapSnapshotStatisticsView._valueFormatter(value);
    }
};

Profiler.HeapAllocationStackView = class extends UI.Widget {
    /**
     * @param {?SDK.HeapProfilerModel} heapProfilerModel
     */
    constructor(heapProfilerModel) {
        super();
        this._heapProfilerModel = heapProfilerModel;
        this._linkifier = new Components.Linkifier();
    }

    /**
     * @param {!Profiler.HeapSnapshotProxy} snapshot
     * @param {number} snapshotNodeIndex
     */
    async setAllocatedObject(snapshot, snapshotNodeIndex) {
        this.clear();
        var frames = await snapshot.allocationStack(snapshotNodeIndex);

        if (!frames) {
            var stackDiv = this.element.createChild('div', 'no-heap-allocation-stack');
            stackDiv.createTextChild(Common.UIString(
                'Stack was not recorded for this object because it had been allocated before this profile recording started.'));
            return;
        }

        var stackDiv = this.element.createChild('div', 'heap-allocation-stack');
        for (var frame of frames) {
            var frameDiv = stackDiv.createChild('div', 'stack-frame');
            var name = frameDiv.createChild('div');
            name.textContent = UI.beautifyFunctionName(frame.functionName);
            if (!frame.scriptId)
                continue;
            var urlElement = this._linkifier.linkifyScriptLocation(
                this._heapProfilerModel ? this._heapProfilerModel.target() : null, String(frame.scriptId), frame.scriptName,
                frame.line - 1, frame.column - 1);
            frameDiv.appendChild(urlElement);
        }
    }

    clear() {
        this.element.removeChildren();
        this._linkifier.reset();
    }
};
